﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using SimpleExpression;
using System.Collections.ObjectModel;
using System.Collections;

namespace SimpleExpression
{
    public abstract class SqlSelectCommandStrategy : SelectCommandStrategy, ISqlSelectCommandStrategy
    {
        private int conditionsCount = 0;
        
        public SqlSelectCommandStrategy(IDatabaseProvider provider)
            :base(provider)
        {
            this.command = new SqlCommand(string.Empty);
        }

        public override void AddAttribute(AttributeExpression expression)
        {
            this.AddSeparator(this.selectClause);

            string propertyName = GetPropertyName(expression);

            this.selectClause.AppendFormat("{0}.{1}", expression.PropertyMetadata.EntityMetadata.Alias, this.provider.Encode(propertyName));
            this.attributesAliases.Add(propertyName);
        }

        public override void AddCondition(ConditionExpression expression)
        {
            Append(expression.LogicalOperator);

            this.whereClause.AppendFormat("{0} {1} {2}", expression.GetConditionField((IDbCommand)this.command, this.provider, expression.Value1), expression.GetSQLConditionOperator(), expression.GetSQLValue((IDbCommand)this.command, this.provider, expression.Value2));
        }

        public override void AddLinkEntity(LinkEntityExpression linkEntity)
        { 
            this.fromClause.Append(SPACE);
            this.fromClause.AppendFormat(Environment.NewLine);            
            this.fromClause.AppendFormat("\t{0} {1}.{2} AS {3}", GetSQLJoinOperator(linkEntity.JoinOperator), this.provider.Encode(linkEntity.EntityTo.Schema), this.provider.Encode(linkEntity.EntityTo.PhysicalName), linkEntity.EntityTo.Alias);
            this.fromClause.AppendFormat(Environment.NewLine);            
            this.fromClause.AppendFormat("\t\tON {0}", this.whereClause.ToString());
            this.fromClause.AppendFormat(Environment.NewLine);
            this.fromClause.Append("\t--");

            this.whereClause.Length = 0;
            this.conditionsCount = 0;
        }

        public override void AddDistinctClause(EntityExpression expression)
        {
            this.distinctClause.Append("DISTINCT");
        }

        public override void AddFromClause(EntityExpression expression)
        {
            this.fromClause.AppendFormat("{0}.{1} AS {2}", this.provider.Encode(expression.EntityMetadata.Schema), this.provider.Encode(expression.EntityMetadata.PhysicalName), expression.EntityMetadata.Alias);
        }

        public override void BeginFilterExpression(FilterExpression expression)
        {
            Append(expression.LogicalOperator);

            this.conditionsCount = 0;
            this.whereClause.Append("( ");
        }

        public override void AddWhereClause(FilterExpression expression)
        {
            //TODO: Review this method
        }

        public override void EndFilterExpression(FilterExpression expression)
        {
            this.whereClause.Append(" )");
        }

        public override void AddGroupByClause(GroupByExpression expression)
        {
            this.groupByClause.Append(string.Join(", ", expression.Attributes.Select(c => string.Format("{0}.{1}", c.PropertyMetadata.Alias, this.provider.Encode(this.GetPropertyName(c)))).ToArray()));
        }

        public override void AddOrderClause(OrderExpression expression)
        {
            this.orderByClause.Append(string.Join(", ", expression.Properties.Select(c => string.Format("{0}.{1} {2}", c.Key.PropertyMetadata.Alias, this.provider.Encode(this.GetPropertyName(c.Key)), c.Value)).ToArray()));
        }

        public override void AddPagingClause(PagingExpression expression)
        {
            throw new NotImplementedException();
        }

        public override void WriteCommand()
        {
            ((IDbCommand)this.Command).CommandText = GetCommandText();
        }

        #region Helper Methods

        protected void AddSeparator(StringBuilder builder)
        {
            if (builder.Length > 0)
                builder.Append(", ");
        }

        protected string GetSQLJoinOperator(JoinOperator joinOperator)
        {
            string result = string.Empty;

            switch (joinOperator)
            {
                case JoinOperator.INNER:
                    result = "INNER JOIN";
                    break;
                case JoinOperator.LEFT:
                    result = "LEFT JOIN";
                    break;
                case JoinOperator.RIGHT:
                    result = "RIGHT JOIN";
                    break;
                case JoinOperator.CROSS:
                    result = "CROSS JOIN";
                    break;
                case JoinOperator.LEFTOUTER:
                    result = "LEFT OUTER JOIN";
                    break;
                case JoinOperator.RIGHTOUTER:
                    result = "LEFT OUTER JOIN";
                    break;
                default:
                    break;
            }

            return result;
        }

        private void Append(LogicalOperator logicalOperator)
        {
            if (this.conditionsCount++ > 0)
                this.whereClause.AppendFormat(" {0} ", logicalOperator.ToString());
        }

        #endregion

        private string GetPropertyName(AttributeExpression attribute)
        {
            PropertyMetadata propertyMetadata = attribute.PropertyMetadata;

            string propertyName = string.Empty;

            if (!(attribute is ViewAttributeExpression))
                return propertyMetadata.PlatformName;
            else
                return propertyMetadata.PhysicalName;
            
        }
    }
}
